Skip to content

feat(io)!: zero-copy send for large writes on io_uring - #152

Merged
lexoliu merged 4 commits into
mainfrom
feat/126-send-zc
Nov 11, 2026
Merged

lexoliu merged 4 commits into
mainfrom
feat/126-send-zc

Conversation

@lexoliu

@lexoliu lexoliu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator

On Linux, a compio TCP write at or above a threshold now goes out with IORING_OP_SENDMSG_ZC. The call returns once the kernel has taken the send. The buffers stay held by a runtime task until the notification completion, then are dropped. Smaller writes still copy.

  • Write::write_all_owned writes gathered buffers in full. It returns them when it copied and keeps them when the kernel holds them. Its default implementation copies, so tokio, async-io and the TLS writers are unchanged. h1's outbox now sends through it.
  • compio::ZeroCopySend is the threshold, a shared runtime setting. A change applies to the next write on every existing stream. It defaults to 10 KiB, the size msg_zerocopy.rst names as where zero-copy starts paying. 0 sends every eligible write zero-copy, and usize::MAX turns it off.
  • Compio now carries the setting and so is no longer Copy. Use Compio::default() or Compio::with_zero_copy(..). Streams are adopted through Compio::tcp_stream.

There is no cap on how many sends a stream can have pending. Zero-copy pages still count toward sk_wmem_queued, so the socket send buffer bounds them as it bounds copied data. Kernel TLS never reaches this path: the kTLS writer only copies, and the kernel's TLS sendmsg rejects MSG_ZEROCOPY with EOPNOTSUPP anyway. A send whose pages would exceed RLIMIT_MEMLOCK fails with ENOMEM before sending anything, and the rest of that write copies. A server that wants zero-copy under load needs CAP_IPC_LOCK or a raised limit.

New tests on Linux check that the notification releases the buffer while the peer is stalled, and that changing the threshold switches existing streams. The io and h1 suites pass. The kTLS suites need CI.

Closes #126

`Write::write_all_owned` writes gathered buffers and hands them back only
when the writer is done with them. On Linux over io_uring, a compio TCP
stream sends a write of at least `ZeroCopySend::threshold` bytes with
`IORING_OP_SENDMSG_ZC` and `MSG_WAITALL`: the call returns at the send's own
completion, and a runtime task holds the operation and its buffers until the
kernel's notification, which drops them. Unacknowledged zero-copy data is
bounded by the socket's send buffer, which counts zero-copy pages. A send
the kernel refuses for `RLIMIT_MEMLOCK` (`ENOMEM`, nothing sent) copies
instead. Unix-domain streams, other drivers and other platforms copy as
before; kernel TLS never reaches the zero-copy path, since its writer only
issues copying writes, which TLS's own sendmsg requires (it rejects
`MSG_ZEROCOPY` with `EOPNOTSUPP`).

`ZeroCopySend` is the runtime-adjustable threshold, shared by every clone
across threads; the default, 10 KiB, is where the kernel's MSG_ZEROCOPY
documentation finds zero-copy pays off. `Compio` now carries it into every
listener and stream it creates, so it is no longer a unit struct, and
`Compio::tcp_stream` replaces `From<compio::net::TcpStream>` for adopting
a stream.
A message's head and data frames go to the writer as owned buffers, so on
io_uring a large flush is sent zero-copy; the buffer list's allocation is
reused only when the writer hands it back.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 81.60920% with 32 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
io/src/stream.rs 73.23% 19 Missing ⚠️
io/src/compio.rs 86.17% 13 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lexoliu
lexoliu merged commit dc1c4cf into main Nov 11, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

io: zero-copy send for large writes on io_uring

2 participants